package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
)

type MG_PRJ_Header struct {
	HeadTag [8]byte //
	FileTag int32   //9

	FileAmount int16 //文件数

	//文件的图形范围 14-45 32byte 8*Double
	XMin float64
	YMin float64
	XMax float64
	YMax float64

	//文件的环境 46-685 640byte 结构
	FileEnv   [640]byte
	FileTitle [60]byte //文件的标题 686-745 60byte
	DictLoc   int32    //图层字典位置 746-749 4byte
	FirstFile int32    //首文件存放指针 750-753 4byte

	TranslateX float64 //位移X 754-761 8byte
	TranslateY float64 //位移Y 762-769 8byte
	ScaleX     float64 //比例X 770-777 8byte
	ScaleY     float64 //比例Y 778-785 8byte
	Rotate     float64 //旋转角度 786-793 8byte

	PageWidth  float64 //版面宽 794-801 8byte
	PageHeight float64 //版面高 802-809 8byte

	PrjType   int16     //810-811 2byte
	LegendLoc int32     //首图例存放指针 812-815 4byte
	MapParams [169]byte //工程预设的地图参数 816-984 169byte

	UnUsed [128]byte //985-1112
}

type MG_Header struct {
	HeadTag      [8]byte //8byte 0-7 GDMP`D20(WL) GDMP`D22（WT）GDMP`D21（WP）
	FileTag      int32   // 4byte 8-11 0=LINE 1=PRT 2=REG
	SegmentStart int32   //4byte 12-15 数据区头的起始位置，从这个整数指向的字节位置存储数据区头信息
	//Unknown_1 int32   //4byte 16-19 此处的整数不知道意义
	SegmentCount int32 //4byte 16-19 数据区的个数
	//Unknow_2 [240]byte //20-259
	Unknown_2_1 [64]byte
	Unknown_2_2 [5]byte
	Unknown_2_3 [20]byte
	Unknown_2_4 [38]byte
	Unknown_2_5 [8]byte
	Unknown_2_6 [8]byte
	Unknown_2_7 [97]byte

	//260-303 44byte
	//存储有11个整数，表示各种数据的总数
	//0-线数    260-263       104h
	//2-节点数  268-271       10Ch
	//4-区数   276-279       114h
	//其余未知
	LineAmount int32
	Unkown_3   int32
	NodeAmount int32
	Unkown_4   int32
	RegAmount  int32
	Unknown_5  int32
	Unknown_6  int32
	Unknown_7  int32
	Unknown_8  int32
	Unknown_9  int32
	Unknown_10 int32

	//304-335 32byte 图形范围
	XMin float64
	YMin float64
	XMax float64
	YMax float64
}

type MG_Data_Header struct {
	// 每个数据头信息10字节，如：29 02 00 00 E4 00 00 00 FF FF
	// 其中
	// Ø  0-3 数据区的起始位置
	// Ø  4-7 数据区总字节数
	// Ø  8-9 结束标记（FF FF）
	DataOffset int32
	DataLength int32
	Sep        [2]byte
}

type MG_Data_Line struct {
	// 线信息 : 每一条线
	// 存储位置：DataHeader[0].DataOffset+59
	// 字节数：57

	Unknown_1         [4]byte
	Unknown_2         [4]byte
	PointAmount       int32   //线点数 4byte 8-11
	CoorStorageOffset int32   //坐标存储位置 4byte 12-15 DataHeader[1]加这个偏移量是坐标的存储起始位置
	Unknown_3         int32   //16-19
	Kind              int16   //线型号 2byte 20-21
	SubKind           byte    //辅助线型号 22
	CoverStyle        byte    //覆盖方式 23
	Color             int32   //线颜色号 4byte 24-27
	Width             float32 //线宽 4byte 28-31
	Category          byte    //线种类 32
	XParam            float32 //X系数 4byte 33-36
	YParam            float32 //Y系数 4byte 37-40
	SecondColor       int32   //辅助色 4byte 41-44
	Layer             int32   //图层 4byte 45-48
	Unknown_4         [8]byte
}

type MG_Data_Coor struct {
	// 存储位置：DataHeader[1].DataOffset+每条线坐标存储位置
	// 字节数：16( 两个八位的浮点数)
	// 格式：
	// X坐标　　0-7
	// Y坐标　　8-15
	X float64
	Y float64
}

type Point struct {
	X float64
	Y float64
}

type Line struct {
	//NodeAmount int
	Nodes []Point
}

type DataSegment struct {
	Index  int
	Header MG_Data_Header
	Data   []byte
}

type Converter struct {
	PrjHeader  MG_PRJ_Header
	FileHeader MG_Header
	Segments   []DataSegment

	FileKind string
	Result   []Line

	mgDataSize int
	mgData     []byte
}

func readNextBytes(file *os.File, number int) []byte {
	bytes := make([]byte, number)

	_, err := file.Read(bytes)
	if err != nil {
		log.Fatal(err)
	}

	return bytes
}

func (conv *Converter) OpenMGProject(path string) {
	file, err := os.Open(path)
	if err != nil {
		log.Fatal("Error while opening project", err)
	}

	defer file.Close()

	data := make([]byte, 1113)

	_, err = file.Read(data)
	if err != nil {
		log.Fatal(err)
	}
	buffer := bytes.NewBuffer(data[:])
	err = binary.Read(buffer, binary.LittleEndian, &conv.PrjHeader)
	if err != nil {
		log.Fatal("Error while reading project data", err)
	}

	fmt.Printf("PROJ parsed: %+v\n", conv.PrjHeader)
}

func (conv *Converter) OpenMGFile(path string) {
	fileInfo, err := os.Stat(path)
	if err != nil {
		log.Fatal("Error while geting file info", err)
	}
	conv.FileKind = strings.ToUpper(filepath.Ext(path))

	var file *os.File

	file, err = os.Open(path)
	if err != nil {
		log.Fatal("Error while opening file", err)
	}

	defer file.Close()

	conv.mgDataSize = int(fileInfo.Size())
	conv.mgData = make([]byte, conv.mgDataSize)

	_, err = file.Read(conv.mgData)
	if err != nil {
		log.Fatal(err)
	}
}

func (conv *Converter) parseHeader() {
	buffer := bytes.NewBuffer(conv.mgData[:336])

	err := binary.Read(buffer, binary.LittleEndian, &(conv.FileHeader))
	if err != nil {
		log.Fatal("binary.Read failed\n", err)
	}
	fmt.Printf("MG_Header: %+v\n", conv.FileHeader)
}

func (conv *Converter) parseSegment(seq int) {
	seg := DataSegment{Index: seq}
	headerPos := int(conv.FileHeader.SegmentStart) + seq*10
	buffer := bytes.NewBuffer(conv.mgData[headerPos : headerPos+10])
	err := binary.Read(buffer, binary.LittleEndian, &(seg.Header))
	if err != nil {
		log.Fatal("Read data header failed", err)
	}
	if seg.Header.DataLength > 0 {
		df := int(seg.Header.DataOffset)
		dt := df + int(seg.Header.DataLength)
		seg.Data = conv.mgData[df:dt]

		conv.Segments = append(conv.Segments, seg)
	}

	if seq < 4 {
		fmt.Printf("MG_Data_Segment: [%v] %v, %v\n", seg.Index, seg.Header, seg.Data[:200])
	} else {
		fmt.Printf("MG_Data_Segment: [%v] %v\n", seg.Index, seg.Header)
	}

}

func (conv *Converter) parseLines() {
	infoSeg := &(conv.Segments[0])
	coorSeg := &(conv.Segments[1])

	conv.Result = make([]Line, 0, conv.FileHeader.LineAmount)

	infoBuf := bytes.NewBuffer(infoSeg.Data[59:])
	var err error

	mgLine := MG_Data_Line{}
	mgCoor := MG_Data_Coor{}
	i := 1
	for {
		err = binary.Read(infoBuf, binary.LittleEndian, &mgLine)
		if err != nil {
			fmt.Printf("Read line data failed: %v\n", err)
			break
		}

		coorOffset := int(mgLine.CoorStorageOffset)
		maxOffset := int(coorSeg.Header.DataLength)
		if coorOffset <= 0 || coorOffset >= maxOffset {
			break
		}

		fmt.Printf("Get a Line: [%v]<%v-%v>==>%v\n ", i, coorOffset, maxOffset, mgLine)

		lx := Line{}
		px := Point{}

		//fmt.Printf("POINTS:")
		coorBuf := bytes.NewBuffer(coorSeg.Data[coorOffset:])
		for j := 0; j < int(mgLine.PointAmount); j++ {
			err = binary.Read(coorBuf, binary.LittleEndian, &mgCoor)
			if err != nil {
				log.Fatal("Read line coordinate failed", err)
			}
			px.X = mgCoor.X
			px.Y = mgCoor.Y
			lx.Nodes = append(lx.Nodes, px)
			// if j < 100 {
			// 	fmt.Printf("(%v,%v) ", px.X, px.Y)
			// }
		}
		//fmt.Printf("\n")
		conv.Result = append(conv.Result, lx)
		i++
	}
}

func (conv *Converter) parsePoints() {
	//TODO: POINTS NOIMPLEMENT
}

func (conv *Converter) ToLines(w io.Writer, srid int) {
	for _, l := range conv.Result {
		fmt.Fprint(w, "ST_GeomFromText('LINESTRING(")
		for i, p := range l.Nodes {
			if i > 0 {
				fmt.Fprint(w, ",")
			}
			fmt.Fprintf(w, "%f %f", 38000000.0+p.X, p.Y) //38000000.0
		}
		fmt.Fprintf(w, ")', %d)\n\n", srid)
	}
}

func (conv *Converter) Convert() { //[]Line
	conv.parseHeader()

	for i := 0; i < int(conv.FileHeader.SegmentCount); i++ {
		conv.parseSegment(i)
	}

	// if conv.FileKind == ".WL" || conv.FileKind == ".WP" {
	// 	conv.parseLines()
	// } else if conv.FileKind == ".WT" {
	// 	conv.parsePoints()
	// }
}
